package cn.nawang.ebeim.server.transport;

import cn.nawang.ebeim.common.entity.FileInfo;
import cn.nawang.ebeim.server.constants.Config;
import cn.nawang.ebeim.server.service.StorageService;
import cn.nawang.ebeim.server.service.StorageServiceFactory;
import cn.nawang.ebeim.server.util.PathUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.util.List;
import java.util.Map;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.*;

public class HttpUploadServerInboundHandler extends SimpleChannelInboundHandler<HttpObject> {

  private static final Logger LOG = LoggerFactory.getLogger(HttpUploadServerInboundHandler.class);
  private File realFile;// 上传成功后的最终文件
  private File file;
  private HttpRequest request;
  private long fileLength;
  private long count;
  private long downLength;// 客户端已经下载的文件大小
  private RandomAccessFile randomFile;
  private static final int MAX_Ping_TIMES = 5;
  private int pingTimes = 0;

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
    cause.printStackTrace();
    IOUtils.closeQuietly(randomFile);
    writeResponse(ctx, INTERNAL_SERVER_ERROR,"unCaught error!");
    ctx.close();
  }

  @Override
  public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if (evt instanceof IdleStateEvent) {
      IdleStateEvent event = (IdleStateEvent) evt;
      if (request!=null && !request.getUri().startsWith("download") && event.state() == IdleState.READER_IDLE) {
        if (randomFile != null) {
          randomFile.close();
        }
        if (pingTimes > MAX_Ping_TIMES) {
          LOG.info("===服务端===(READER_IDLE 读超时)");
          ctx.channel().close();
        } else {
          pingTimes++;
        }
      } else if (event.state() == IdleState.WRITER_IDLE) {
        LOG.info("===服务端===(WRITER_IDLE 写超时)");
      } else if (event.state() == IdleState.ALL_IDLE) {
        LOG.info("===服务端===(ALL_IDLE 总超时)");
      }
    }
  }

  @Override
  protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
    // 读取到时候置0
    pingTimes = 0;
    if (msg instanceof HttpRequest) {
      request = (HttpRequest) msg;
      if (request.getUri().startsWith("download")) {
        // 客户端请求下载， 将服务端的文件发送到客户端
        downLoadResponse(ctx);
      } else {
        getFile(); // 生成临时文件名和最终文件名
        if (request.getUri().startsWith("query")) {
          long total = request.headers().getContentLength(request);
          long uploadLength = 0;
          if (file.exists() && file.isFile()) {
            uploadLength = file.length();
            // 如果临时文件大于文件本身则删除文件。
            if (uploadLength >= total) {
              file.delete();
              uploadLength = 0;
            }
            HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, OK);
            response.headers().set("CONNECTION", HttpHeaders.Values.KEEP_ALIVE);
            HttpHeaders.setContentLength(response, uploadLength);
            ChannelFuture future = ctx.writeAndFlush(response);
            future.addListener(ChannelFutureListener.CLOSE);
          }
        } else {
          fileLength = request.headers().getContentLength(request);
          randomFile = new RandomAccessFile(file, "rw");
          long uploadLength = randomFile.length();
          randomFile.seek(uploadLength);
        }
      }
    }
    if (msg instanceof HttpContent) {
      // 接收客户端上传的文件
      if (request.getUri().startsWith("upload")) {
        HttpContent chunk = (HttpContent) msg;
        ByteBuf buf = chunk.content();
        count += buf.readableBytes();
        if (fileLength != 0) {
          double rate = count * 100 / fileLength;
          LOG.debug("服务端接收客户端上传文件进度：" + rate);
        }
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        randomFile.write(req);
        if (chunk instanceof LastHttpContent) {
          IOUtils.closeQuietly(randomFile);
          FileInputStream in = null ;
          try{
            in = new FileInputStream(file);
            String newMd5 = DigestUtils.md5Hex(in);
            IOUtils.closeQuietly(in);
            String md5String = getMd5String();
            if (!newMd5.equals(md5String)) {
              LOG.info("MD5验证失败！" + file.getPath() + ",删除临时文件");
              file.delete();
              writeResponse(ctx, INTERNAL_SERVER_ERROR, "md5 failed");
              return;
            }
            boolean bool = file.renameTo(realFile);
            if (!bool) {
              LOG.info("重命名失败！" + file.getPath() + ",删除临时文件");
              file.delete();
              writeResponse(ctx, INTERNAL_SERVER_ERROR,"rename failed");
            } else {
              LOG.info("服务端已经成功接收客户端上传的文件：" + realFile.getPath());
              writeResponse(ctx, OK);
            }
          }catch (Exception e){
            throw e;
          }finally {

          }

        }
      }
    }
  }



  /**
   * 
   * @author: huanghj
   * @Description: 将服务端的文件发送到客户端
   * @param @param ctx
   * @param @throws Exception
   * @return void
   * @date 2015年9月17日下午2:30:04
   *
   * @修改人
   * @修改日期
   */
  private void downLoadResponse(ChannelHandlerContext ctx) throws Exception {
    final RandomAccessFile raf;
    try{
      file = getDownFile();
      if (!file.exists()){
        writeResponse(ctx, NOT_FOUND,"file not found");
        return;
      }
    }catch (Exception e){
      writeResponse(ctx, INTERNAL_SERVER_ERROR,"swift server error");
      return;
    }
    FileInputStream in = new FileInputStream(file);
    String md5 = DigestUtils.md5Hex(in);
    in.close();
    LOG.info(file.getAbsolutePath() + "的大小:" + file.length());
    raf = new RandomAccessFile(file, "r");
    long count = raf.length() - downLength;
    HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
    HttpHeaders.setContentLength(response, count);
    response.headers().set("md5", md5);
    setContentTypeHeader(response, file);
    if (HttpHeaders.isKeepAlive(request)) {
      response.headers().set("CONNECTION", HttpHeaders.Values.KEEP_ALIVE);
    }
    ctx.write(response);
    ChannelFuture sendFileFuture;
    sendFileFuture = ctx.channel().write(new DefaultFileRegion(raf.getChannel(), downLength, count),
        ctx.newProgressivePromise());
    sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
      @Override
      public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
        if (total < 0) {
          LOG.debug(future.channel() + " Transfer progress: " + progress);
        } else {
          LOG.debug(future.channel() + " Transfer progress: " + progress + " / " + total);
        }
      }

      @Override
      public void operationComplete(ChannelProgressiveFuture future) throws IOException {
        raf.close();
        if (future.isSuccess()) {
          boolean delete = file.delete();
          LOG.info(future.channel() + " 已经传输到客户端完成,删除临时文件:" + file +",删除结果:" + delete);
        } else {
          LOG.info(future.channel() + " 已经传输到客户端失败:{}", future.cause());
        }
      }
    });

    ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
  }

  /**
   * 
   * @author: huanghj
   * @Description: 获取云端下载到服务端的文件名
   * @param @return
   * @param @throws Exception
   * @return File
   * @date 2015年9月17日下午2:27:19
   *
   * @修改人
   * @修改日期
   */
  public File getDownFile() throws Exception {
    String localPath = null;
    QueryStringDecoder decoderQuery = new QueryStringDecoder(request.getUri());
    Map<String, List<String>> uriAttributes = decoderQuery.parameters();
    // 获取客户端已经下载的文件的字节数
    downLength = Long.parseLong(uriAttributes.get("downLength").get(0));
    String type = uriAttributes.get("type").get(0);
    String dataId = uriAttributes.get("dataId").get(0);
    StorageService storageService = StorageServiceFactory.getStorageService();
    // 判断服务端是否存在文件
    if (type.equals(String.valueOf(FileInfo.TYPE_FILE_DOWNLOAD))) {
      String signature = uriAttributes.get("signature").get(0);
      localPath = PathUtil.getFileLocalPathDownload(dataId, signature);
      File f = new File(localPath);
      if (f.exists()) {
        return f;
      } else {
        storageService.downloadFile(dataId, signature);
      }
    } else if (type.equals(String.valueOf(FileInfo.TYPE_VERSION_DOWNLOAD))) {
      String version = uriAttributes.get("version").get(0);
      localPath = PathUtil.getVersionTreeLocalPathDownLoad(dataId, version);
      File f = new File(localPath);
      if (f.exists()) {
        return f;
      } else {
        storageService.downloadVersionTree(dataId, version);
      }
    }
    LOG.info("从云端下载到服务端地址：" + localPath);
    return new File(localPath);
  }

  public String getMd5String() {
    QueryStringDecoder decoderQuery = new QueryStringDecoder(request.getUri());
    Map<String, List<String>> uriAttributes = decoderQuery.parameters();
    String md5 = uriAttributes.get("md5").get(0);
    return md5;
  }

  /**
   * 
   * @author: huanghj
   * @Description: 生成临时文件名和最终生成的文件名
   * @param @throws Exception
   * @return void
   * @date 2015年9月17日下午2:26:21
   *
   * @修改人
   * @修改日期
   */
  public void getFile() throws Exception {
    QueryStringDecoder decoderQuery = new QueryStringDecoder(request.getUri());
    Map<String, List<String>> uriAttributes = decoderQuery.parameters();
    String type = uriAttributes.get("type").get(0);
    String fileName = null;
    File dir = null;
    String dataId = uriAttributes.get("dataId").get(0);
    if (type.equals(String.valueOf(FileInfo.TYPE_FILE_UPLOAD))) {
      fileName = uriAttributes.get("signature").get(0);
      dir = new File(Config.WORKING_DIR + Config.FILE_UPLOAD + File.separator + dataId);
      // 最终生成的文件
      String newName = Config.WORKING_DIR + Config.FILE_UPLOAD + File.separator + dataId
          + File.separator + fileName;
      realFile = new File(newName);

    } else if (type.equals(String.valueOf(FileInfo.TYPE_VERSION_UPLOAD))) {
      fileName = uriAttributes.get("version").get(0);
      dir = new File(Config.WORKING_DIR + Config.VERSION_UPLOAD + File.separator + dataId);
      // 最终生成的文件
      String newName = Config.WORKING_DIR + Config.VERSION_UPLOAD + File.separator + dataId
          + File.separator + fileName;
      realFile = new File(newName);
    }
    if (!dir.exists()) {
      dir.mkdirs();
    }
    file = new File(dir + File.separator + Config.TEMP_FILE_PREFIX + fileName);
    // 创建上级目录
    if (!file.getParentFile().exists()) {
      file.getParentFile().mkdir();
    }
    // 创建目标文件
    if (!file.exists()) {
      file.createNewFile();
    }
    LOG.info("成功创建文件(" + dir.getPath() + File.separator + fileName + " )准备写入数据");
  }

  /**
   * 
   * @author: huanghj
   * @Description: 关闭连接
   * @param @param ctx
   * @return void
   * @date 2015年9月17日下午2:34:06
   *
   * @修改人
   * @修改日期
   */
  private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status) {
    HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
    ChannelFuture future = ctx.writeAndFlush(response);
    future.addListener(ChannelFutureListener.CLOSE);
    ctx.close();
  }
  private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status,String error) {
    HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, status);
    response.headers().set("error", error);
    ChannelFuture future = ctx.writeAndFlush(response);
    future.addListener(ChannelFutureListener.CLOSE);
    ctx.close();
  }

  private void setContentTypeHeader(HttpResponse response, File file) {
    MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
    response.headers().set(CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));
  }
}
